/*********************************************************
 *	 A tiny BASIC interpreter adapted from:	         *
 *                                                       *
 *		C: Power User's Guide                    *
 *			by                               *
 * 		  Herbert Schildt                        *
 *                                                       *
 *	  Published by Osborne/McGraw-Hill,              *
 *	Copyright 1987, Osborne/McGraw-Hill.             *
 *********************************************************/

#include <stdio.h>
#include <setjmp.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifdef MSDOS
#include <dos.h>
#include <stdlib.h>
#else
#include "term.h"
#endif


#include "defines.h"

jmp_buf e_buf;			/* hold environment for longjmp() */

struct commands {		/* keyword lookup table */
	char	command[20];	/* command name */
	char	tokenid;	/* command token value */
} table[] = {			/* entries here must be lowercase */
	"print", 	PRINT,
	"input", 	INPUT,
	"if", 		IF,
	"then", 	THEN,
	"goto", 	GOTO,
	"for", 		FOR,
	"next", 	NEXT,
	"to", 		TO,
	"gosub", 	GOSUB,
	"return", 	RETURN,
	"shell", 	SHELL,
	"move", 	MOVE,
	"cls", 		CLS,
	"rem", 		REMARK,
	"beep", 	BEEP,
	"sleep", 	SLEEP,
	"time",		TIME,
	"date",		DATE,
	"end", 		END,
	"", 		END	/* mark end of table */
};


struct for_stack {
	int	var;		/* counter variable */
	int	target;		/* target value */
	char	*loc;
} fstack[FOR_NEST];		/* stack for FOR/NEXT loop */

struct for_stack fpop();

char	token[BUFSIZ];
char	*p_buf;
char	*gpop();
char	*load();
char	*prog;			/* holds expression to be analyzed */
char	*gstack[SUB_NEST];	/* stack for gosub */

int	tokentype;
int	tokenstat;
int	ftos;			/* index to top of FOR stack */
int	gtos;			/* index to top of gosub stack */

int	variables[26] = {	/* 26 user variables,  A-Z */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};


void b_print(), geteol(), b_goto(), b_sleep();
void b_if (), b_for(), b_next(), fpush(), b_input();
void getexp(), level2(), level3(), level4(), level5();
void level6(), primitive(), arith(), unary(), b_shell();
void b_error(), putback(), b_gosub(), b_return(), b_remark();
void gpush(), b_move(), b_cls(), b_beep(), b_time(), b_date();

main(argc, argv)
int	argc;
char	*argv[];
{

	if (argc != 2) {
		fprintf (stderr, "usage: basic <progname>[.bas]\n");
		exit(1);
	}

	setbuf(stdin, NULL);
	setbuf(stdout, NULL);
	setbuf(stderr, NULL);

#ifndef MSDOS
	gettermtype();
#endif
	/* load the program to execute */
	if ((p_buf = load(argv[1])) == NULL) {
		fprintf(stderr, "\nBASIC: can not load %s", argv[1]);
		exit(1);
	}

	/* initialize the long jump buffer */
	if (setjmp(e_buf))
		exit(1);
	prog = p_buf;
	initlabels(token);	/* find the labels in the program */
	ftos = 0;		/* initialize the FOR stack index */
	gtos = 0;		/* initialize the gosub stack index */

	do {
		/* get first token to get ball rolling */
		tokentype = gettoken(token);

		/* check for assignment statement */
		if (tokentype == VARIABLE) {
			putback(token);		/* put token back */
			assignment(token);	/* must be assignment */
		} else {			/* token must be a command */
			switch (tokenstat) {
			case PRINT:
				b_print(token);
				break;
			case GOTO:
				b_goto(token);
				break;
			case IF:
				b_if(token);
				break;
			case FOR:
				b_for(token);
				break;
			case NEXT:
				b_next();
				break;
			case INPUT:
				b_input(token);
				break;
			case GOSUB:
				b_gosub(token);
				break;
			case RETURN:
				b_return();
				break;
			case SHELL:
				b_shell(token);
				break;
			case CLS:
				b_cls();
				break;
			case MOVE:
				b_move(token);
				break;
			case REMARK:
				b_remark();
				break;
			case BEEP:
				b_beep(token);
				break;
			case SLEEP:
				b_sleep(token);
				break;
			case TIME:
				b_time();
				break;
			case DATE:
				b_date();
				break;
			case END:
				b_cls();
				tokenstat = FINISHED;
				break;
			}
		}	/* end of 'else' */
	} while (tokenstat != FINISHED);
	exit(0);
}


/*
 * load - allocate sufficient memory if possible,
 * then load the user BASIC program into memory.
 * returns pointer to program in allocated memory.
 */
char	*
load(fname)
char	*fname;
{
	int	fp, r;
	char	ifile[15], *b;
	struct stat st;

	strcpy(ifile, fname);			/* save partial name */
	if (!strchr(ifile, '.'))		/* check for extension */
		strcat(ifile, ".bas");		/* add ".bas" to file name */

	/* attempt to open file */
	if ((fp = open (ifile, O_RDONLY | O_TEXT)) == EOF) {
		fprintf (stderr, "\nBASIC: program open error: %s\n", ifile);
		return (NULL);
	}

	/* determine amount of memory required */
	if (fstat (fp, &st)) {
		fprintf (stderr, "\nBASIC: program status error\n");
		close (fp);
		return (NULL);
	} else
		fprintf(stderr, "\nBASIC: program size: %d\n", st.st_size);

	/* allocate memory for the program */
	if ((b = (char *)calloc(1, st.st_size + 1)) == NULL) {
		fprintf (stderr, "\nBASIC: memory allocation failure\n");
		close (fp);
		return (NULL);
	}

	/* read file into zeroed memory */
	r = read (fp, b, (unsigned )st.st_size);

	/* check for EOF instead of amount read */
	/* in consideration of the MSDOS O_TEXT */
	/* mode, which could be less due to CRLF */
	/* translation to LF only */
	if (r == EOF) {
		fprintf(stderr, "\nBASIC: program file load error\n");
		close (fp);
		return (NULL);
	} else
		fprintf(stderr, "BASIC: loaded size: %d\n", r);

	close (fp);		/* done with file, so close it */
	return (b);		/* return pointer to memory */
}


/*
 * gettoken - get a token and place it in gtoken.
 */
int
gettoken(gtoken)
char	*gtoken;
{
	char	*temp;

	tokentype	 = 0;
	tokenstat	 = 0;
	temp		 = gtoken;

	if (*prog == NULL) {		/* end of file */
		gtoken[0] = NULL;
		tokenstat = FINISHED;
		return(DELIMITER);
	}

	while (iswhite(*prog))
		++prog;			/* skip over white space */

	if (*prog == '\n') {		/* LF */
		++prog;
		if (*prog)
			tokenstat = EOL;
		else
			tokenstat = FINISHED;
		gtoken[0] = '\n';
		gtoken[1] = NULL;
		return (DELIMITER);
	}

	if (strchr("+-*^/%=;(),><", *prog)) {	/* delimiter */
		*temp = *prog;		/* white space already skipped */
		prog++;			/* advance to next position */
		temp++;
		*temp = NULL;
		return (DELIMITER);
	}

	if (*prog == '"') {			/* quoted string */
		prog++;				/* step over quote */

		while (*prog && *prog != '"' && *prog != '\n')
			*temp++ = *prog++;	/* copy string to temp */

		if (*prog == '\n' || *prog == NULL)
			b_error(16);		/* unbalanced quotes */

		prog++;				/* step over end quote */
		*temp = NULL;			/* terminate temp */
		return(QUOTE);
	}

	if (isdigit(*prog)) {			/* number */
		while (!isdelim(*prog))
			*temp++ = *prog++;
		*temp = NULL;
		return(NUMBER);
	}

	if (isalpha(*prog)) {			/* var or command */
		while (!isdelim(*prog))
			*temp++ = *prog++;
		tokentype = STRING;
	}

	*temp = NULL;

	/* see if a string is a command or a variable */
	if (tokentype == STRING) {
		tokenstat = look_up(gtoken);	/* convert to internal rep */
		if (!tokenstat)
			tokentype = VARIABLE;
		else
			tokentype = COMMAND;	/* is a command */
	}
	return (tokentype);
}



/*
 * assignment - assign a value to a variable 
 */
assignment(atoken)
char	*atoken;
{
	int	var, value;

	/* get the variable name */
	tokentype = gettoken(atoken);
	if (!isalpha (*atoken)) {
		b_error(4);
		return;
	}

	var = toupper(*atoken) - 'A';	/* calculate the variable's offset */

	/* get the equals sign */
	tokentype = gettoken(atoken);
	if (*atoken != '=') {
		b_error(3);
		return;
	}

	/* get the value to assign to variable */
	getexp(atoken, &value);

	/* assign the value */
	variables[var] = value;
	return;
}


/*
 * geteol - find the start of the next line
 */
void
geteol()
{
	while (*prog != '\n'  && *prog != NULL)
		++prog;
	if (*prog)
		prog++;
	return;
}



/*
 * b_if - execute an IF statement
 */
void
b_if(itoken)
char	*itoken;
{
	int	x , y, cond;
	char	op;

	getexp(itoken, &x);			/* get left expression */

	tokentype = gettoken(itoken);		/* get the operator */
	if (!strchr("=<>", *itoken)) {
		b_error(0);			/* not a legal operator */
		return;
	}
	op = *itoken;

	getexp(itoken, &y);			/* get right expression */

	cond = 0;				/* determine the outcome */
	switch (op) {
	case '<':
		if (x < y)
			cond = 1;
		break;
	case '>':
		if (x > y)
			cond = 1;
		break;
	case '=':
		if (x == y)
			cond = 1;
		break;
	}
	if (cond) {			/* true, so process target of IF */
		tokentype = gettoken(itoken);
		if (tokenstat != THEN) {
			b_error(8);
			return;
		}
	} else	/* execution starts on next line */
		geteol();		/* find start of next line */
	return;
}


/*
 * b_for - execute a FOR loop
 */
void
b_for(ftoken)
char	*ftoken;
{
	struct for_stack i;
	int	value;

	tokentype = gettoken(ftoken);		/* read the control variable */
	if (!isalpha(*ftoken)) {
		b_error(4);
		return;
	}

	i.var = toupper(*ftoken) - 'A';		/* save its index */

	tokentype = gettoken(ftoken);		/* read the equals sign */
	if (*ftoken != '=') {
		b_error(3);
		return;
	}

	getexp(ftoken, &value);			/* get initial value */

	variables[i.var] = value;

	tokentype = gettoken(ftoken);
	if (tokenstat != TO)
		b_error(9);		/* read and discard the TO */

	getexp(ftoken, &i.target);	/* get target value */

	/* if loop can execute at least once, push info on stack */
	if (value >= variables[i.var]) {
		i.loc = prog;
		fpush(i);
	} else	/* otherwise, skip loop code */
		while (tokenstat != NEXT)
			tokentype = gettoken(ftoken);
	return;
}


/*
 * next - execute a NEXT statement
 */
void
b_next()
{
	struct for_stack i;

	i = fpop();			/* read the loop info */

	variables[i.var]++;		/* increment control variable */
	if (variables[i.var] > i.target)
		return;			/* all done */
	fpush(i);			/* otherwise, restore the info */
	prog = i.loc;			/* loop */
	return;
}


/*
 * fpush - push function for the FOR stack
 */
void
fpush(i)
struct for_stack i;
{
	if (ftos > FOR_NEST)
		b_error(10);

	fstack[ftos] = i;
	ftos++;
	return;
}


/*
 * fpop - pop function from the FOR stack
 */
struct for_stack fpop()
{
	ftos--;
	if (ftos < 0)
		b_error(11);
	return(fstack[ftos]);
}



/*
 * gpush - gosub stack push function
 */
void
gpush(s)
char	*s;
{
	gtos++;

	if (gtos == SUB_NEST) {
		b_error(12);
		return;
	}

	gstack[gtos] = s;
	return;
}


/*
 * gpop - gosub stack pop function
 */
char	*
gpop()
{
	if (gtos == 0) {
		b_error(13);
		return 0;
	}
	return(gstack[gtos--]);
}


/*
 * getexp - entry point into parser
 */
void
getexp(xtoken, result)
char	*xtoken;
int	*result;
{
	tokentype = gettoken(xtoken);
	if (!*xtoken) {
		b_error(2);
		return;
	}
	level2(xtoken, result);
	putback(xtoken); 		/* return last token to input */
	return;
}


/*
 * level2 - Add or subtract two terms
 */
void
level2(xtoken, result)
char	*xtoken;
int	*result;
{
	register char	op;
	int	hold;

	level3(xtoken, result);
	while ((op = *xtoken) == '+' || op == '-') {
		tokentype = gettoken(xtoken);
		level3(xtoken, &hold);
		arith(op, result, &hold);
	}
	return;
}


/*
 * level3 - multiply or divide two factors. 
 */
void
level3(xtoken, result)
char	*xtoken;
int	*result;
{
	register char	op;
	int	hold;

	level4(xtoken, result);
	while ((op = *xtoken) == '*' || op == '/' || op == '%') {
		tokentype = gettoken(xtoken);
		level4(xtoken, &hold);
		arith(op, result, &hold);
	}
	return;
}


/*
 * level4 - process integer exponent.
 */
void
level4(xtoken, result)
char	*xtoken;
int	*result;
{
	int	hold;

	level5(xtoken, result);
	if (*xtoken ==  '^') {
		tokentype = gettoken(xtoken);
		level4(xtoken, &hold);
		arith('^', result, &hold);
	}
	return;
}


/*
 * level5 - process a unary + or -.
 */
void
level5(xtoken, result)
char	*xtoken;
int	*result;
{
	register char	op;

	op = NULL;
	if ((tokentype == DELIMITER) && *xtoken == '+' || *xtoken == '-') {
		op = *xtoken;
		tokentype = gettoken(xtoken);
	}
	level6(xtoken, result);
	if (op)
		unary(op, result);
	return;
}


/*
 * level6 - process parenthesized expression.
 */
void
level6(xtoken, result)
char	*xtoken;
int	*result;
{
	if ((*xtoken == '(') && (tokentype == DELIMITER)) {
		tokentype = gettoken(xtoken);
		level2(xtoken, result);
		if (*xtoken != ')')
			b_error(1);
		tokentype = gettoken(xtoken);
	} else
		primitive(xtoken, result);
	return;
}


/*
 * primitive - find value of number or variable.
 */
void
primitive(xtoken, result)
char	*xtoken;
int	*result;
{

	switch (tokentype) {
	case VARIABLE:
		*result = getval(xtoken);
		tokentype = gettoken(xtoken);
		return;
	case NUMBER:
		*result = atoi(xtoken);
		tokentype = gettoken(xtoken);
		return;
	default:
		b_error(0);
	}
	return;
}


/*
 * arith - perform the specified arithmetic.
 */
void
arith(o, r, h)
char	o;
int	*r, *h;
{
	register int	t, ex;

	switch (o) {
	case '-':
		*r = *r - *h;
		break;
	case '+':
		*r = *r + *h;
		break;
	case '*':
		*r = *r * *h;
		break;
	case '/':
		*r = (*r) / (*h);
		break;
	case '%':
		t = (*r) / (*h);
		*r = *r - (t * (*h));
		break;
	case '^':
		ex = *r;
		if (*h == 0) {
			*r = 1;
			break;
		}
		for (t = *h - 1; t > 0; --t)
			*r = (*r) * ex;
		break;
	}
	return;
}


/*
 * unary - reverse the sign.
 */
void
unary(o, r)
char	o;
int	*r;
{
	if (o == '-')
		*r = -(*r);
	return;
}


/*
 * getval - find the value of a variable.
 */
int
getval(s)
char	*s;
{
	if (!isalpha(*s)) {
		b_error(4);		/* not a variable */
		return 0;		/* NOTREACHED */
	}
	return variables[toupper(*s)-'A'];
}




/*
 * putback - return a token to input stream.
 */
void
putback(ptoken)
char	*ptoken;
{

	char	*t;

	t = ptoken;
	for (; *t; t++)
		prog--;
	return;
}


/*
 * b_error - display an error message.
 */
void
b_error(error)
int	error;
{
	static char	*e[] = {
		"syntax error", 			/*  0 */
		"unbalanced parentheses", 		/*  1 */
		"no expression present", 		/*  2 */
		"equals sign expected", 		/*  3 */
		"not a variable", 			/*  4 */
		"label table full", 			/*  5 */
		"duplicate label", 			/*  6 */
		"undefined label", 			/*  7 */
		"THEN expected", 			/*  8 */
		"TO expected", 				/*  9 */
		"too many nested FOR loops", 		/* 10 */
		"NEXT without FOR", 			/* 11 */
		"too many nested GOSUBs", 		/* 12 */
		"RETURN without GOSUB", 		/* 13 */
		"invalid row number", 			/* 14 */
		"invalid column number", 		/* 15 */
		"unbalanced double quotes"		/* 16 */
	};
	fprintf (stderr, "\nBASIC: %s\n", e[error]);
	longjmp(e_buf, 1);	/* return to save point */
}


/*
 * loop_up - find a token's internal representation in the token table.
 */
int
look_up(s)
char	*s;
{
	register int	i;
	char	*p;

	/* convert to lowercase */
	p = s;
	while (*p) {
		*p = tolower(*p);
		p++;
	}

	/* see if token is in table */
	for (i = 0; *table[i].command; i++)
		if (strcmp(table[i].command, s) == 0)
			return (table[i].tokenid);	/* valid command */

	return(0);					/* unknown command */
}


/*
 * isdelim - return true if c is a delimiter.
 */
isdelim(c)
char	c;
{
	if (strchr(" ;,+-<>/*%^=()", c) || c == 9 || c == '\n' || c == 0)
		return 1;
	return 0;
}


/*
 * iswhite - return 1 if c is space or tab.
 */
iswhite(c)
char	c;
{
	if (c == ' ' || c == '\t')
		return 1;
	else
		return 0;
}


